package com.gitee.wsl.lang.bytearray


/**
 * Interface for validating byte arrays
 */
interface BytesValidator {
    /**
     * Validates given byte array
     *
     * @param byteArrayToValidate array, must not be altered, only read
     * @return true if validation is successful, false otherwise
     */
    fun validate(byteArrayToValidate: ByteArray): Boolean

    /**
     * Validates for specific array length
     */
    class Length(
        private val refLength: Int,
        private val mode: Mode,
    ) : BytesValidator {
         enum class Mode {
            SMALLER_OR_EQ_THAN, GREATER_OR_EQ_THAN, EXACT
        }

        override fun validate(byteArrayToValidate: ByteArray): Boolean {
            return when (mode) {
                Mode.GREATER_OR_EQ_THAN -> byteArrayToValidate.size >= refLength
                Mode.SMALLER_OR_EQ_THAN -> byteArrayToValidate.size <= refLength
                Mode.EXACT -> byteArrayToValidate.size == refLength
                //else -> return byteArrayToValidate.size == refLength
            }
        }
    }

    /**
     * Checks if a byte array contains only the same value
     */
    class IdenticalContent(private val refByte: Byte, private val mode: Mode) : BytesValidator {
         enum class Mode {
            ONLY_OF, NONE_OF, NOT_ONLY_OF
        }

        override fun validate(byteArrayToValidate: ByteArray): Boolean {
            for (b in byteArrayToValidate) {
                if (mode == Mode.NONE_OF && b == refByte) {
                    return false
                }
                if (mode == Mode.ONLY_OF && b != refByte) {
                    return false
                }
                if (mode == Mode.NOT_ONLY_OF && b != refByte) {
                    return true
                }
            }
            return mode == Mode.NONE_OF || mode == Mode.ONLY_OF
        }
    }

    /**
     * Checks if arrays end or start with given array
     */
    class PrePostFix(
        private val startsWith: Boolean,
        private vararg val pfix: Byte,
    ) : BytesValidator {
        override fun validate(byteArrayToValidate: ByteArray): Boolean {
            if (pfix.size > byteArrayToValidate.size) {
                return false
            }

            for (i in pfix.indices) {
                if (startsWith && pfix[i] != byteArrayToValidate[i]) {
                    return false
                }
                if (!startsWith && pfix[i] != byteArrayToValidate[byteArrayToValidate.size - pfix.size + i]) {
                    return false
                }
            }
            return true
        }
    }

    /**
     * Logical operations over multiple validators
     */
    class Logical(
        validatorList: List<BytesValidator>,
        operator: Operator,
    ) : BytesValidator {

        enum class Operator {
            OR, AND, NOT
        }

        private val validatorList: List<BytesValidator>

        private val operator: Operator

        init {
            require(!validatorList.isEmpty()) { "must contain at least 1 element" }
            require(!(operator == Operator.NOT && validatorList.size != 1)) { "not operator can only be applied to single element" }
            this.validatorList = validatorList
            this.operator = operator
        }

        override fun validate(byteArrayToValidate: ByteArray): Boolean {
            if (operator == Operator.NOT) {
                return !validatorList.get(0).validate(byteArrayToValidate)
            }

            var bool = operator != Operator.OR
            for (bytesValidator in validatorList) {
                when (operator) {
                    Operator.AND -> bool = bool and bytesValidator.validate(byteArrayToValidate)
                    Operator.OR -> bool = bool or bytesValidator.validate(byteArrayToValidate)
                    else -> bool = bool or bytesValidator.validate(byteArrayToValidate)
                }
            }
            return bool
        }
    }

    companion object{

        /**
         * Checks the length of a byte array
         *
         * @param byteLength to check against
         * @return validator that returns true if longer or equal to given value
         */
        fun atLeast(byteLength: Int): BytesValidator {
            return Length(byteLength, Length.Mode.GREATER_OR_EQ_THAN)
        }

        /**
         * Checks the length of a byte array
         *
         * @param byteLength to check against
         * @return validator that returns true if smaller or equal to given value
         */
        fun atMost(byteLength: Int): BytesValidator {
            return Length(byteLength, Length.Mode.SMALLER_OR_EQ_THAN)
        }

        /**
         * Checks the length of a byte array
         *
         * @param byteLength to check against
         * @return validator that returns true if equal to given value
         */
        fun exactLength(byteLength: Int): BytesValidator {
            return Length(byteLength, Length.Mode.EXACT)
        }

        /**
         * Checks individual byte content
         *
         * @param refByte to check against
         * @return validator that returns true if array only consists of refByte
         */
        fun onlyOf(refByte: Byte): BytesValidator {
            return IdenticalContent(refByte, IdenticalContent.Mode.ONLY_OF)
        }

        /**
         * Checks individual byte content
         *
         * @param refByte to check against
         * @return validator that returns true  if array has at least one byte that is not refByte
         */
        fun notOnlyOf(refByte: Byte): BytesValidator {
            return IdenticalContent(refByte, IdenticalContent.Mode.NOT_ONLY_OF)
        }

        /**
         * Checks if the internal byte array starts with given bytes
         *
         * @param startsWithBytes the supposed prefix
         * @return validator that returns true all startsWithBytes match the first bytes in the internal array
         */
        fun startsWith(vararg startsWithBytes: Byte): BytesValidator {
            return PrePostFix(true, *startsWithBytes)
        }

        /**
         * Checks if the internal byte array ends with given bytes
         *
         * @param endsWithBytes the supposed postfix
         * @return validator that returns true all startsWithBytes match the first bytes in the internal array
         */
        fun endsWith(vararg endsWithBytes: Byte): BytesValidator {
            return PrePostFix(false, *endsWithBytes)
        }

        /**
         * Checks individual byte content
         *
         * @param refByte to check against
         * @return validator that returns true if array has no value refByte
         */
        fun noneOf(refByte: Byte): BytesValidator {
            return IdenticalContent(refByte, IdenticalContent.Mode.NONE_OF)
        }

        /**
         * This will execute all passed validators and returns true if at least one returns true (i.e. OR concatenation)
         *
         * @param validators at least one validator must be passed
         * @return validator that returns true if at least one validator returns true
         */
        fun or(vararg validators: BytesValidator): BytesValidator {
            return Logical(validators.toList(), Logical.Operator.OR)
        }

        /**
         * This will execute all passed validators and returns true if all return true (i.e. AND concatenation)
         *
         * @param validators at least one validator must be passed
         * @return validator that returns true if all return true
         */
        fun and(vararg validators: BytesValidator): BytesValidator {
            return Logical(validators.toList(), Logical.Operator.AND)
        }

        /**
         * This will negate the result of the passed validator
         *
         * @param validator to negate
         * @return negated result
         */
        fun not(validator: BytesValidator): BytesValidator {
            return Logical(listOf(validator), Logical.Operator.NOT)
        }
    }
}
